/*
 * $Id: string_strfnmat.c,v 1.25 2005-12-14 08:50:26 bacon Exp $
 */

#if defined(__MSTRING)
#include <xp/bas/mstring.h>
#elif defined(__WSTRING)
#include <xp/bas/wstring.h>
#else
#include <xp/bas/string.h>
#endif
#include <xp/bas/ctype.h>

#define SEPARATOR XP_CHAR('/')
#define ESCAPER   XP_CHAR('\\')
#define PERIOD    XP_CHAR('.')

static xp_bool_t __xp_strxnfnmat (
	const xp_char_t* str, xp_size_t slen,
	const xp_char_t* ptn, xp_size_t plen, int flags, 
	int no_first_period);

xp_bool_t xp_strfnmat (const xp_char_t* str, const xp_char_t* ptn, int flags)
{
	return __xp_strxnfnmat (
		str, xp_strlen(str), ptn, xp_strlen(ptn), flags, 0);
}

xp_bool_t xp_strxfnmat  (
	const xp_char_t* str, xp_size_t slen, const xp_char_t* ptn, int flags)
{
	return __xp_strxnfnmat (str, slen, ptn, xp_strlen(ptn), flags, 0);
}

xp_bool_t xp_strnfnmat  (
	const xp_char_t* str, const xp_char_t* ptn, xp_size_t plen, int flags)
{
	return __xp_strxnfnmat (str, xp_strlen(str), ptn, plen, flags, 0);
}

xp_bool_t xp_strxnfnmat (
	const xp_char_t* str, xp_size_t slen,
	const xp_char_t* ptn, xp_size_t plen, int flags)
{
	return __xp_strxnfnmat (str, slen, ptn, plen, flags, 0);
}

static xp_bool_t __xp_strxnfnmat (
	const xp_char_t* str, xp_size_t slen,
	const xp_char_t* ptn, xp_size_t plen, int flags, int no_first_period)
{
	const xp_char_t* sp = str;
	const xp_char_t* pp = ptn;
	const xp_char_t* se = str + slen;
	const xp_char_t* pe = ptn + plen;
	xp_char_t sc, pc, pc2;

	while (1) {
		if (pp < pe && *pp == ESCAPER && 
		    (flags & XP_STRFNMAT_NOESCAPE) == 0) {
			/* pattern is escaped and escaping is allowed. */

			if ((++pp) >= pe) {
				/* 
				 * the last character of the pattern is an ESCAPER. 
				 * matching is performed as if the end of the pattern is
				 * reached just without an ESCAPER.
				 */
				if (sp < se) return xp_false;
				return xp_true;
			}

			if (sp >= se) return xp_false; /* premature string termination */

			sc = *sp; pc = *pp; /* pc is just a normal character */
			if ((flags & XP_STRFNMAT_CASEFOLD) != 0) {
				/* make characters to lower-case */
				sc = xp_tolower(sc); pc = xp_tolower(pc); 
			}

			if (sc != pc) return xp_false;
			sp++; pp++; 
			continue;
		}
		if (pp >= pe) {
			/* 
			 * the end of the pattern has been reached. 
			 * the string must terminate too.
			 */
			return sp >= se;
		}

		if (sp >= se) {
			/* the string terminats prematurely */
			while (pp < pe && *pp == XP_CHAR('*')) pp++;
			return pp >= pe;
		}

		sc = *sp; pc = *pp;

		if (sc == PERIOD && (flags & XP_STRFNMAT_PERIOD) != 0) {
			/* 
			 * a leading period in the staring must match 
			 * a period in the pattern explicitly 
			 */
			if ((no_first_period == 0 && sp == str) || 
			    (sp[-1] == SEPARATOR && (flags & XP_STRFNMAT_PATHNAME) != 0)) {
				if (pc != PERIOD) return xp_false;
				sp++; pp++;
				continue;
			}
		}
		else if (sc == SEPARATOR && (flags & XP_STRFNMAT_PATHNAME) != 0) {
			while (pc == XP_CHAR('*')) {
				if ((++pp) >= pe) return xp_false;
				pc = *pp;
			}

			/* a path separator must be matched explicitly */
			if (pc != SEPARATOR) return xp_false;
			sp++; pp++;
			continue;
		}

		/* the handling of special pattern characters begins here */
		if (pc == XP_CHAR('?')) { 
			/* match any single character */
			sp++; pp++; 
		} 
		else if (pc == XP_CHAR('*')) { 
			/* match zero or more characters */

			/* compact asterisks */
			do { pp++; } while (pp < pe && *pp == XP_CHAR('*'));

			if (pp >= pe) {
				/* 
				 * if the last character in the pattern is an asterisk,
				 * the string should not have any directory separators
				 * when XP_STRFNMAT_PATHNAME is set.
				 */
				return ((flags & XP_STRFNMAT_PATHNAME) == 0)? 
					xp_true: (xp_strchr(sp, SEPARATOR) == XP_NULL);
			}
			else {
				do {
					if (__xp_strxnfnmat(sp, se - sp, pp, pe - pp, flags, 1)) {
						return xp_true;
					}

					if (*sp == SEPARATOR && 
					    (flags & XP_STRFNMAT_PATHNAME) != 0) break;

					sp++;
				} while (sp < se);

				return xp_false;
			}
		}
		else if (pc == XP_CHAR('[')) {
			/* match range */
			xp_bool_t negate = xp_false;
			xp_bool_t matched = xp_false;

			if ((++pp) >= pe) return xp_false;
			if (*pp == XP_CHAR('!')) { negate = xp_true; pp++; } 

			while (pp < pe && *pp != XP_CHAR(']')) {
				if (*pp == XP_CHAR('[')) { 
					xp_size_t pl = pe - pp;

					if (pl >= 10) {
						if (xp_strcmpn(pp, XP_TEXT("[:xdigit:]"), 10) == 0) {
							matched = xp_isxdigit(sc);
							pp += 10; continue;
						}
					}

					if (pl >= 9) {
						if (xp_strcmpn(pp, XP_TEXT("[:upper:]"), 9) == 0) {
							matched = xp_isupper(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:lower:]"), 9) == 0) {
							matched = xp_islower(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:alpha:]"), 9) == 0) {
							matched = xp_isalpha(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:digit:]"), 9) == 0) {
							matched = xp_isdigit(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:alnum:]"), 9) == 0) {
							matched = xp_isalnum(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:space:]"), 9) == 0) {
							matched = xp_isspace(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:print:]"), 9) == 0) {
							matched = xp_isprint(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:graph:]"), 9) == 0) {
							matched = xp_isgraph(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:cntrl:]"), 9) == 0) {
							matched = xp_iscntrl(sc);
							pp += 9; continue;
						}
						else if (xp_strcmpn(pp, XP_TEXT("[:punct:]"), 9) == 0) {
							matched = xp_ispunct(sc);
							pp += 9; continue;
						}
					}

					/* 
					 * characters in an invalid class name are 
					 * just treated as normal characters 
					 */
				}

				if (*pp == ESCAPER && 
				    (flags & XP_STRFNMAT_NOESCAPE) == 0) pp++;
				else if (*pp == XP_CHAR(']')) break;

				if (pp >= pe) break;

				pc = *pp;
				if ((flags & XP_STRFNMAT_CASEFOLD) != 0) {
					sc = xp_tolower(sc); 
					pc = xp_tolower(pc); 
				}

				if (pp + 1 < pe && pp[1] == XP_CHAR('-')) {
					pp += 2; /* move the a character next to a dash */

					if (pp >= pe) {
						if (sc >= pc) matched = xp_true;
						break;
					}

					if (*pp == ESCAPER && 
					    (flags & XP_STRFNMAT_NOESCAPE) == 0) {
						if ((++pp) >= pe) {
							if (sc >= pc) matched = xp_true;
							break;
						}
					}
					else if (*pp == XP_CHAR(']')) {
						if (sc >= pc) matched = xp_true;
						break;
					}

					pc2 = *pp;
					if ((flags & XP_STRFNMAT_CASEFOLD) != 0) 
						pc2 = xp_tolower(pc2); 

					if (sc >= pc && sc <= pc2) matched = xp_true;
					pp++;
				}
				else {
					if (sc == pc) matched = xp_true;
					pp++;
				}
			}

			if (negate) matched = !matched;
			if (!matched) return xp_false;
			sp++; if (pp < pe) pp++;
		}
		else {
			/* a normal character */
			if ((flags & XP_STRFNMAT_CASEFOLD) != 0) {
				sc = xp_tolower(sc); pc = xp_tolower(pc); 
			}

			if (sc != pc) return xp_false;
			sp++; pp++;
		}
	}

	/* will never reach here. but make some immature compilers happy... */
	return xp_false;
}
